mod extend;

use crate::extend::AsStrType;
use prost::Message;
use prost_types::field_descriptor_proto::Label;
use prost_types::{
    DescriptorProto, EnumDescriptorProto, FieldDescriptorProto, FileDescriptorProto, FileOptions,
    ServiceDescriptorProto, SourceCodeInfo,
};
use std::collections::HashMap;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum ConverterError {
    #[error("Proto decoding error")]
    DecodeError(#[from] prost::DecodeError),
    #[error("Internal extend error")]
    ExtendError(#[from] extend::ExtendError),
    #[error("{0}")]
    GenericError(String),
}

enum Syntax {
    Proto2,
    Proto3,
}

pub fn process_file_descriptor_set(
    file_descriptor_set_data: &[u8],
) -> Result<HashMap<String, String>, ConverterError> {
    let mut descriptors = HashMap::new();
    let file_descriptor_set = prost_types::FileDescriptorSet::decode(file_descriptor_set_data)?;
    for descriptor_proto in &file_descriptor_set.file {
        let (file_name, content) = process_file_descriptor(descriptor_proto)?;
        descriptors.insert(file_name, content);
    }
    Ok(descriptors)
}

fn process_file_descriptor(
    descriptor: &FileDescriptorProto,
) -> Result<(String, String), ConverterError> {
    let mut buffer = String::with_capacity(1024 * 1024); // 1MB
    let syntax = process_version(&mut buffer, descriptor.syntax.as_ref());
    let name = descriptor.name().to_string();
    process_package(&mut buffer, descriptor.package.as_ref());

    if let Some(options) = &descriptor.options {
        process_options(&mut buffer, options)?;
    }

    for dependency in &descriptor.dependency {
        buffer.push_str(format!("import \"{dependency}\";\n").as_str());
    }
    buffer.push('\n');

    // TODO: Hande public_dependency and weak_dependency

    process_enum(&mut buffer, &descriptor.enum_type)?;
    process_message(
        &mut buffer,
        &syntax,
        &descriptor.message_type,
        descriptor.package.as_ref(),
    )?;
    process_service(&mut buffer, &descriptor.service)?;
    process_extension(&mut buffer, &descriptor.extension)?;

    if let Some(source_code_info) = &descriptor.source_code_info {
        process_source_code_info(&mut buffer, source_code_info)?;
    }

    Ok((name, buffer))
}

fn process_version(buffer: &mut String, syntax: Option<&String>) -> Syntax {
    // According to google, if no edition or syntax is specified, one shall default to proto2
    let syntax = syntax.cloned().unwrap_or("proto2".to_string());
    buffer.push_str(format!("syntax = \"{syntax}\";\n\n").as_str());
    match syntax.as_str() {
        "proto3" => Syntax::Proto3,
        _ => Syntax::Proto2,
    }
}

fn process_package(buffer: &mut String, package: Option<&String>) {
    if let Some(package) = package {
        buffer.push_str(format!("package {package};\n\n").as_str());
    }
}

fn process_message(
    buffer: &mut String,
    syntax: &Syntax,
    messages: &[DescriptorProto],
    package: Option<&String>,
) -> Result<(), ConverterError> {
    for message in messages {
        buffer.push_str(format!("message {} {{\n", message.name()).as_str());
        for field in &message.field {
            buffer.push('\t');
            // TODO: use label to know if optional shall be used
            match field.label() {
                Label::Optional => buffer.push_str("optional "),
                Label::Required => match syntax {
                    Syntax::Proto2 => buffer.push_str("required "),
                    Syntax::Proto3 => {}
                },
                Label::Repeated => buffer.push_str("repeated "),
            }
            buffer.push_str(field.r#type().as_str_type(field.type_name.as_ref())?);
            buffer.push_str(format!(" {} = {}", field.name(), field.number()).as_str());

            if let Some(extendee) = &field.extendee {
                // TODO: Handle extendee
                return Err(ConverterError::GenericError(format!(
                    "Descriptor extendee is not supported {extendee:?}"
                )));
            }

            if let Some(default_value) = &field.default_value {
                // TODO: Handle default_value
                return Err(ConverterError::GenericError(format!(
                    "Descriptor default_value is not supported {default_value:?}"
                )));
            }

            if let Some(oneof_index) = &field.oneof_index {
                // TODO: Handle oneof_index
                return Err(ConverterError::GenericError(format!(
                    "Descriptor oneof_index is not supported {oneof_index:?}"
                )));
            }

            if let Some(json_name) = &field.json_name {
                buffer.push_str(format!(" [json_name=\"{json_name}\"]").as_str());
            }
            buffer.push_str(";\n");
        }
        process_extension(buffer, &message.extension)?;
        // TODO: We might require indentation without external tool
        process_message(buffer, syntax, &message.nested_type, package)?;
        // TODO: We might require indentation without external tool
        process_enum(buffer, &message.enum_type)?;

        if !message.extension_range.is_empty() {
            // TODO: Handle extension_range
            return Err(ConverterError::GenericError(format!(
                "Descriptor extension_range is not supported {:?}",
                message.extension_range
            )));
        }

        if !message.oneof_decl.is_empty() {
            // TODO: Handle oneof_decl
            return Err(ConverterError::GenericError(format!(
                "Descriptor oneof_decl is not supported {:?}",
                message.oneof_decl
            )));
        }

        if let Some(options) = &message.options {
            // TODO: Handle options
            return Err(ConverterError::GenericError(format!(
                "Descriptor options is not supported {options:?}"
            )));
        }

        if !message.reserved_range.is_empty() {
            // TODO: Handle reserved_range
            return Err(ConverterError::GenericError(format!(
                "Descriptor reserved_range is not supported {:?}",
                message.reserved_range
            )));
        }

        if !message.reserved_name.is_empty() {
            // TODO: Handle reserved_name
            return Err(ConverterError::GenericError(format!(
                "Descriptor reserved_name is not supported {:?}",
                message.reserved_name
            )));
        }

        buffer.push_str("}\n\n");
    }
    Ok(())
}

fn process_enum(buffer: &mut String, enums: &[EnumDescriptorProto]) -> Result<(), ConverterError> {
    for enum_descriptor in enums {
        buffer.push_str(format!("enum {} {{\n", enum_descriptor.name()).as_str());
        for variant in &enum_descriptor.value {
            buffer.push_str(format!("\t{} = {};\n", variant.name(), variant.number()).as_str());
            if let Some(options) = &variant.options {
                // TODO: Handle options
                return Err(ConverterError::GenericError(format!(
                    "Descriptor options is not supported {options:?}"
                )));
            }
        }

        if let Some(options) = &enum_descriptor.options {
            // TODO: Handle options
            return Err(ConverterError::GenericError(format!(
                "Descriptor options is not supported {options:?}"
            )));
        }

        if !enum_descriptor.reserved_range.is_empty() {
            // TODO: Handle reserved_range
            return Err(ConverterError::GenericError(format!(
                "Descriptor reserved_range is not supported {:?}",
                enum_descriptor.reserved_range
            )));
        }

        if !enum_descriptor.reserved_name.is_empty() {
            // TODO: Handle reserved_name
            return Err(ConverterError::GenericError(format!(
                "Descriptor reserved_name is not supported {:?}",
                enum_descriptor.reserved_name
            )));
        }

        buffer.push_str("}\n\n");
    }
    Ok(())
}

fn process_service(
    _buffer: &mut String,
    services: &[ServiceDescriptorProto],
) -> Result<(), ConverterError> {
    if !services.is_empty() {
        // TODO: Handle service
        return Err(ConverterError::GenericError(format!(
            "Descriptor service is not supported {services:?}"
        )));
    }
    Ok(())
}

fn process_options(_buffer: &mut String, options: &FileOptions) -> Result<(), ConverterError> {
    // TODO: Handle options
    Err(ConverterError::GenericError(format!(
        "Descriptor options is not supported {options:?}"
    )))
}

fn process_extension(
    _buffer: &mut String,
    extension: &[FieldDescriptorProto],
) -> Result<(), ConverterError> {
    if !extension.is_empty() {
        // TODO: Handle extension
        return Err(ConverterError::GenericError(format!(
            "Descriptor extension is not supported {extension:?}"
        )));
    }
    Ok(())
}

fn process_source_code_info(
    _buffer: &mut String,
    source_code_info: &SourceCodeInfo,
) -> Result<(), ConverterError> {
    // TODO: Handle source_code_info
    Err(ConverterError::GenericError(format!(
        "Descriptor source_code_info is not supported {source_code_info:?}"
    )))
}
